//!
//! @file $RCSfile: classB.c,v $
//!
//! Copyright (c) 2006 Atmel.
//!
//! @brief This module provides the IEC 60730 Class B functional \n
//!                                      and periodic test
//! @version $Revision: 1.5 $
//!

//_____  I N C L U D E S ___________________________________________________
#include "ioavr.h"
#include "inavr.h"

#include "classb.h"
#include <classB_config.h>



//_____ M A C R O S ________________________________________________________


//_____ D E F I N I T I O N S ______________________________________________

#define WDT_TIMER   0x18

// Test result returns
#define TEST_OK     1
#define TEST_FAIL   0

//_____ D E C L A R A T I O N S ____________________________________________
 
__no_init unsigned char LastResetReason;
unsigned char Interrupt_Test_Flag;

/**
 * @brief Interrupt controller test
 *       Use an interruption which is not used by the application
 * @pre Configuration the dedicated interruption (initialization)
 * @post Set the Interrupt_Test_Flag
 */
#pragma vector = TIMER0_OVF_vect
__interrupt void unused_interrupt(void)
{
  Interrupt_Test_Flag = 1;
}

//! @brief Finds last reset reason  \n
//!   and saves it into variable LastResetReason \n
//!  
//!
void Find_Reset_Reason(void)
{
  unsigned char i, mask;
  // Find Reset Reason
  mask = 0x01;
  for ( i=0;i<5;i++ )
  {
    if ( MCUCSR & mask )
    {
      break;
    }
    mask = mask << 1;
  }
  LastResetReason = i;      // for reset reason definitions see classB.h
}

//! @brief the purpose of this function is to test the Watchdog reset \n
//!   to be sure the part is resetted on watchdog request \n
//!   on power up watchdog is activated and we wait until reset occurs \n
//!   on watchdog reset function return TRUE \n
//!  
//! @return result TEST_FAIL or TEST_OK
//!
static unsigned char watchdog_test(void)
{
  unsigned char result = TEST_FAIL;
  if ( !(LastResetReason & RESET_WAITING) )  // Intentionally reset
  {
    Find_Reset_Reason();    // Find reset reason and store it into variable Last_Reset_Reason
  }
  if( (MCUCSR & 0x08) == 0x08)  // if Watchdog reset occurs
  {
    result = TEST_OK;
  }
  else              //  if reset occur (not from WDT) start watchdog
  {
    MCUCSR = 0;                   // clear MCU control and status register
    WDTCR |= (1<<WDTOE | 1<<WDE); // security write
    WDTCR =  WDT_TIMER;           // start prescaler
    LastResetReason |= RESET_WAITING;
    while(1);                     // wait reset
  }
  LastResetReason = LastResetReason & (RESET_WAITING ^ 0xFF);
  MCUCSR = 0;                     // clear MCU control and status register
  return result;
}


//! @brief the purpose of this function is to test the CPU Status register \n
//!   limit: Bit I global interrupt is not tested here \n
//!  
//! @return cpu_status_result a 8 bit value where the bits setted correspond to the wrong
//! bit in status register (see example) \n
//! return value = 0   Test OK \n
//! return value = 0x02 then bit 1(Z) is KO \n
//!
static unsigned char cpu_status_test(void)
{
  unsigned char a,sav_sreg,cpu_status_result=0;

   /* Status register test */
   sav_sreg = SREG;
   SREG = 0x55;
   a=SREG;
   SREG=sav_sreg;
   if (a != 0x55)
   { 
      cpu_status_result=TEST_FAIL;
   }
   else
   {
      sav_sreg = SREG;
      SREG = 0x2A;       /* General IT will be tested in IT test */
      a=SREG;
      SREG=sav_sreg;
      if (a == 0x2A)
      { 
         cpu_status_result=TEST_OK;
      }
      else
      {
         cpu_status_result=TEST_FAIL;
      }
   }
   return cpu_status_result;
}


//! @brief * the purpose of this function is to test the Internal Sram
//! Sram is filled with 0x55 and 0xAA to test if there is no stuck bit
//! March B test is implemented in two area to with test overlapp beetwen both area
//! \n return value = 0   Test OK
//! \n return value != 0 Test KO
//! to do :  add some code to save SRAM value from STACK
static unsigned char sram_test(void)
{   
  register unsigned char *p_val,*p_sav,i,sav;
  register unsigned char val;
  register signed  char h; 
 
  /* test all locations with 0x55, 0xAA and complement of address value */
  for (p_val=(unsigned char *)SRAM_START_ADR;p_val<((unsigned char *)(SRAM_START_ADR+SRAM_SIZE));p_val++)
    {
      sav = *p_val;
      *p_val = 0x55;
      i = *p_val;
      if (i!=0x55) { return TEST_FAIL; }
      *p_val = 0xAA;
      i = *p_val;
      if (i!=0xAA) { return TEST_FAIL; } 
      val = ~((unsigned char)p_val);
      *p_val = val;
      i = *p_val;
      if (i != val) { return TEST_FAIL; } 
      *p_val=sav;
    }
 
        
      /* test 1 */ 
  /* erase all sram */
  for (p_val=(unsigned char *)(SRAM_START_ADR+SRAM_SIZE);p_val>=((unsigned char *)SRAM_START_PAGE2);p_val--)
     {  
       *p_val=0; 
     }
       /* test 2 */ 
  for (p_val=(unsigned char *)SRAM_START_PAGE2;p_val<((unsigned char *)(SRAM_START_ADR+SRAM_SIZE));p_val++)
     {  
       for (h=0;h<8;h++)
       {
         val =(((*p_val)>>h) & 0x01);  // read 0
         if (val!= 0x00) 
         {
           return TEST_FAIL;
         }
         
         val = *p_val | (1<<h);
         *p_val= val;  // write 1
         
         val =(((*p_val)>>h) & 0x01);  // read 1 
         if (val!= 0x01) 
         {
           return TEST_FAIL;
         }
   
         val =  *p_val  & ~(1<<h);
         *p_val= val;  // write 0
         
         val =(((*p_val)>>h) & 0x01);  // read 0 
         if (val!= 0x00) 
         {
           return TEST_FAIL;
         } 
         
          *p_val=(*p_val | (1<<h));  // write 1
         }
     }
   
   /* test 3 */  
  for (p_val=(unsigned char *)SRAM_START_PAGE2;p_val<((unsigned char *)(SRAM_START_ADR+SRAM_SIZE));p_val++)
     {  
       for (h=0;h<8;h++)
       {
    
         
         val =(((*p_val)>>h) & 0x01);  // read 1 
         if (val!= 0x01) 
         {
           return TEST_FAIL;
         }
   
         val =  *p_val  & ~(1<<h);
         *p_val= val;  // write 0
         
         *p_val=(*p_val | (1<<h));  // write 1
     
         }
     }
   
   /* test 4 */ 
  for (p_val=(unsigned char *)(SRAM_START_ADR+SRAM_SIZE-1);p_val>=((unsigned char *)SRAM_START_PAGE2);p_val--) 
  {  
       for (h=7;h>=0;h--)
       {
        
         val =(((*p_val)>>h) & 0x01);  // read 1 
         if (val!= 0x01) 
         {
           return TEST_FAIL;
         }
    
         val =  *p_val & ~(1<<h);
         *p_val= val;  // write 0
         
         *p_val=(*p_val | (1<<h));  // write 1
         
         val =  *p_val & ~(1<<h);
         *p_val= val;  // write 0
         
         }
     }
   
  /* test 5 */ 
  for (p_val=(unsigned char *)(SRAM_START_ADR+SRAM_SIZE-1);p_val>=((unsigned char *)SRAM_START_PAGE2);p_val--)  
  {  
     for (h=7;h>=0;h--)
       {
        
         val =(((*p_val)>>h) & 0x01);  // read 0 
         if (val!= 0x00) 
         {
           return TEST_FAIL;
         } 
                  
         *p_val=(*p_val | (1<<h));  // write 1
         
         val =  *p_val & ~(1<<h);
         *p_val= val;  // write 0
         
         }
     }
  
  
  /* test on area from SRAM_START_ADR to SRAM_START_PAGE2 */
   
     /* save area from SRAM_START_ADR to SRAM_START_PAGE2 on top of RAM */
     p_sav = (unsigned char *)SRAM_END_ADR;
     for (p_val = ((unsigned char *)SRAM_START_ADR); \
          p_val <= (unsigned char *)(SRAM_START_PAGE2-1); \
          p_val ++)
     { 
       *(p_sav--) = *(p_val) ;
     }
     
     /* save the stack pointer */
     *(p_sav--)=SPH;
     *(p_sav--)=SPL;
     /* use a new stack pointer during test */
     SP = (unsigned int)p_sav;
  
            
      /* test 1 - 2nd Area */ 
  for (p_val=(unsigned char *)(SRAM_START_PAGE2+15);p_val>=((unsigned char *)SRAM_START_ADR);p_val--)
     {  
       *p_val=0; 
     }
       /* test 2 - 2nd Area*/ 
  for (p_val=(unsigned char *)SRAM_START_ADR;p_val<((unsigned char *)(SRAM_START_PAGE2+15));p_val++)
     {  
       for (h=0;h<8;h++)
       {
         val =(((*p_val)>>h) & 0x01);  // read 0
         if (val!= 0x00) 
         {
           return TEST_FAIL;
         }
         
         val = *p_val | (1<<h);
         *p_val= val;  // write 1
         
         val =(((*p_val)>>h) & 0x01);  // read 1 
         if (val!= 0x01) 
         {
           return TEST_FAIL;
         }
   
         val =  *p_val  & ~(1<<h);
         *p_val= val;  // write 0
         
         val =(((*p_val)>>h) & 0x01);  // read 0 
         if (val!= 0x00) 
         {
           return TEST_FAIL;
         } 
         
          *p_val=(*p_val | (1<<h));  // write 1
         }
     }
   
   /* test 3 - 2nd Area */  
  for (p_val=(unsigned char *)SRAM_START_ADR;p_val<((unsigned char *)(SRAM_START_PAGE2+15));p_val++)
     {  
       for (h=0;h<8;h++)
       {
    
         
         val =(((*p_val)>>h) & 0x01);  // read 1 
         if (val!= 0x01) 
         {
           return TEST_FAIL;
         }
   
         val =  *p_val  & ~(1<<h);
         *p_val= val;  // write 0
         
         *p_val=(*p_val | (1<<h));  // write 1
     
         }
     }
   
   /* test 4 - 2nd Area */ 
  for (p_val=(unsigned char *)(SRAM_START_PAGE2+14);p_val>=((unsigned char *)SRAM_START_ADR);p_val--)    
  {  
       for (h=7;h>=0;h--)
       {
        
         val =(((*p_val)>>h) & 0x01);  // read 1 
         if (val!= 0x01) 
         {
           return TEST_FAIL;
         }
    
         val =  *p_val & ~(1<<h);
         *p_val= val;  // write 0
         
         *p_val=(*p_val | (1<<h));  // write 1
         
         val =  *p_val & ~(1<<h);
         *p_val= val;  // write 0
         
         }
     }
   
  /* test 5 - 2nd Area */ 
  for (p_val=(unsigned char *)(SRAM_START_PAGE2+14);p_val>=((unsigned char *)SRAM_START_ADR);p_val--)   
  {  
     for (h=7;h>=0;h--)
       {
        
         val =(((*p_val)>>h) & 0x01);  // read 0 
         if (val!= 0x00) 
         {
           return TEST_FAIL;
         } 
                  
         *p_val=(*p_val | (1<<h));  // write 1
         
         val =  *p_val & ~(1<<h);
         *p_val= val;  // write 0
         
         }
     } 


  /* restore area from SRAM_START_ADR to SRAM_START_PAGE2 from top of RAM */
    p_sav = (unsigned char *)SRAM_END_ADR;
  for (p_val = ((unsigned char *)SRAM_START_ADR); \
       p_val <= (unsigned char *)(SRAM_START_PAGE2-1); \
       p_val ++)
  { 
    *(p_val) = *(p_sav--) ;
  }
  /* restore stack pointer */
  sav = *(p_sav--);
  i = *(p_sav--);
  SP = (sav<<8) + i;
     
     
  return TEST_OK;
}



//! @brief the purpose of this function is to test the timer 0 
//!                                                and its interrupt outputs
//! the function returns a 8 bit value 
//!  return value = TEST_OK or TEST_FAIL
static unsigned char timer0_test(void)
{
  register unsigned char i,t,val,u;
  
  TIMSK = 0x55;
  if (TIMSK != 0x55) return TEST_FAIL;
  TIMSK = 0xAA;
  if (TIMSK != 0xAA) return TEST_FAIL;
  TIMSK = 0;
  
  OCR0 = 0x55;
  if (OCR0 != 0x55) return TEST_FAIL;
  OCR0 = 0xAA;
  if (OCR0 != 0xAA) return TEST_FAIL;
  OCR0 = 0;
  
  TCNT0 = 0x55;
  if (TCNT0 != 0x55) return TEST_FAIL;
  TCNT0 = 0xAA;
  if (TCNT0 != 0xAA) return TEST_FAIL;
  
  TCCR0 = 0x55;
  if (TCCR0!= 0x55) return TEST_FAIL;
  TCCR0 = 0xAA;
  if (TCCR0 != 0x2A) return TEST_FAIL;
  
  /* test the timer 0 interrupt signals */
  TCNT0 = 150;
  OCR0 = 200;
  TCCR0 = 1; /* start timer 0 */
  
  i=0;
  t=0;
  if ((TIFR & 0x03)!=0) return TEST_FAIL;
  while (i < 200) /* features a time-out */
  {
    val = (TIFR & 0x03);
    u = ((t<<4)+val); /* merge state and TIFR value */
    switch (u)
    {
    case 0x00:
      /* stay in initial state */
      break;
    case 0x02:
      /* an OCF0 occurred */
      /* go to state 2 */
      t = 2;
      break;
    case 0x22:
      /* stay in state 2 */
      break;
    case 0x23:
      /* an TOV0 occurred */
      /* test ok - end of while */
      i=250; /* generate an ok signal */
      break;
    default:
      /* all others cases are fault */
      i=210; /* generate an error signal */
    }
    i++;
  }
  TCCR0 = 0; /* stop timer 0 */
  if (i<220)  return TEST_FAIL;
  return TEST_OK;
}



//! @brief the purpose of this function is to test the timer 1 
//!                                               and its interrupt outputs
//! the function returns a 8 bit value 
//! return value = TEST_OK or TEST_FAIL
static unsigned char timer1_test(void)
{
  register unsigned char i,t,val,u;
  
  OCR1A = 0x5555;
  if (OCR1A != 0x5555) return TEST_FAIL;
  OCR1A = 0xAAAA;
  if (OCR1A != 0xAAAA) return TEST_FAIL;
  OCR1A = 0;
  
  OCR1B = 0x5555;
  if (OCR1B != 0x5555) return TEST_FAIL;
  OCR1B = 0xAAAA;
  if (OCR1B != 0xAAAA) return TEST_FAIL;
  OCR1B = 0xFF00;
  
  ICR1 = 0x5555;
  if (ICR1 != 0x5555) return TEST_FAIL;
  ICR1 = 0xAAAA;
  if (ICR1 != 0xAAAA) return TEST_FAIL;
  ICR1 = 0;
  
  TCNT1 = 0x5555;
  if (TCNT1 != 0x5555) return TEST_FAIL;
  TCNT1 = 0xAAAA;
  if (TCNT1 != 0xAAAA) return TEST_FAIL;
  
  TCCR1A = 0x55;
  if (TCCR1A != 0x51) return TEST_FAIL;
  TCCR1A = 0xAA;
  if (TCCR1A != 0xA2) return TEST_FAIL;
  TCCR1A = 0;
  
  TCCR1B = 0x55;
  if (TCCR1B != 0x55) return TEST_FAIL;
  TCCR1B = 0xAA;
  if (TCCR1B != 0x8A) return TEST_FAIL;
  
  /* test the timer 1 interrupt signals */
  TCNT1H = 0xFF;
  TCNT1L = 100;
  OCR1AH = 0xFF;
  OCR1AL = 150;
  OCR1BH = 0xFF;
  OCR1BL = 200;
  TCCR1B = 1; /* start timer 1 */
  
  i=0;
  t=0;
  if ((TIFR & 0x3C)!=0) return TEST_FAIL;
  while (i < 200) /* features a time-out */
  {
    val = ((TIFR>>2) & 0x0F);
    u = ((t<<4)+val); /* merge state and TIFR value */
    switch (u)
    {
    case 0x00:
      /* stay in initial state */
      break;
    case 0x04:
      /* an OCF1A occurred */
      /* go to state 4 */
      t = 4;
      break;
    case 0x44:
      /* stay in state 4 */
      break;
    case 0x46:
      /* an OCF1B occurred */
      /* go to state 6 */
      t = 6;
      break;
    case 0x66:
      /* stay in state 6 */
    break;
    case 0x67:
      /* an TOV1 occurred */
      /* test ok - end of while */
      i=250; /* generate an ok signal */
      break;
    default:
      /* all others cases are fault */
      i=210; /* generate an error signal */
    }
    i++;
  }
  TCCR1B = 0; /* stop timer 1 */
  if (i<220)  return TEST_FAIL;
  return TEST_OK;
}


//! @brief the purpose of this function is to test the timer 2 
//!                                               and its interrupt outputs
//! the function returns a 8 bit value 
//! return value = TEST_OK or TEST_FAIL
static unsigned char timer2_test(void)
{
  register unsigned char i,t,val,u;
  
  OCR2 = 0x55;
  if (OCR2 != 0x55) return TEST_FAIL;
  OCR2 = 0xAA;
  if (OCR2 != 0xAA) return TEST_FAIL;
  OCR2 = 0;
  
  TCNT2 = 0x55;
  if (TCNT2 != 0x55) return TEST_FAIL;
  TCNT2 = 0xAA;
  if (TCNT2 != 0xAA) return TEST_FAIL;
  
  TCCR2 = 0x55;
  if (TCCR2!= 0x55) return TEST_FAIL;
  TCCR2 = 0xAA;
  if (TCCR2 != 0x2A) return TEST_FAIL;
  
  TCNT2 = 150;
  OCR2 = 200;
  TCCR2 = 1; /* start timer 2 */
  
  /* test the timer 2 interrupt signals */
  i=0;
  t=0;
  if (((TIFR>>6) & 0x03)!=0) return TEST_FAIL;
  while (i < 200) /* features a time-out */
  {
    val = ((TIFR>>6) & 0x03);
    u = ((t<<4)+val); /* merge state and TIFR value */
    switch (u)
    {
    case 0x00:
      /* stay in initial state */
      break;
    case 0x02:
      /* an OCF0 occurred */
      /* go to state 2 */
      t = 2;
      break;
    case 0x22:
      /* stay in state 2 */
      break;
    case 0x23:
      /* an TOV0 occurred */
      /* test ok - end of while */
      i=250; /* generate an ok signal */
      break;
    default:
      /* all others cases are fault */
      i=210; /* generate an error signal */
    }
    i++;
  }
  TCCR2 = 0; /* stop timer 2 */
  if (i<220)  return TEST_FAIL;
  return TEST_OK;
}

//! @brief the purpose of this function is to test the interrupt controller 
//! @pre TOV0 is already set by timer 0 test
static unsigned char interrupt_test(void)
{ 
  Interrupt_Test_Flag = 0;
  TIMSK = (1<<TOIE0); /* enable the selected interrupt */
                      /* for example, Timer 0 overflow */
  __enable_interrupt();
  __no_operation();   /* give one cycle */
  __disable_interrupt();
  if (Interrupt_Test_Flag == 1)  return TEST_OK;
  return TEST_FAIL;
}

//! @brief main entry point of the class B tests \n
//!   One by one startup tests\n
//! @return result TEST_FAIL or TEST_OK
//!  
void Startup_Test(void)
{
  char Test_Result = TEST_OK, state, loopend = 0;
  state = 0;
  while ( (Test_Result!=TEST_FAIL) && (!loopend) ){
    switch ( state ){
      case 0:
        Test_Result = watchdog_test();
      break;
      case 1:
        Test_Result = cpu_status_test();
      break;
      case 2:
        Test_Result = sram_test();
      break;
      case 3:
        Test_Result = timer0_test();
      break;
      case 4:
        Test_Result = timer1_test();
      break;
      case 5:
        Test_Result = timer2_test();
      break;
      case 6:
        Test_Result = interrupt_test();
      break;
      default:
        loopend = 1;
      break;
    }
    state++;
  }
  if ( Test_Result == TEST_FAIL ){
    while(1);                   /* infinite loop if class B test has failed */
  }
}
